import { Tabs } from 'nextra/components';
import { Widget } from '@/components/demo/widget.tsx';
import LinkBadge from '@/components/ui/link-badge/link-badge.tsx';
import LinkBadgeGroup from '@/components/ui/link-badge/link-badge-group.tsx';

# Popover

A popover displays rich content in a portal that is aligned to a child.

<LinkBadgeGroup>
    <LinkBadge label="API Reference" href="https://pub.dev/documentation/forui/latest/forui.widgets.popover/"/>
</LinkBadgeGroup>

<Tabs items={['Preview', 'Code']}>
  <Tabs.Tab>
    <Widget name='popover' query={{alignment: 'topCenter'}} height={500}/>
  </Tabs.Tab>
  <Tabs.Tab>
    ```dart copy
    class _Popover extends StatelessWidget {
      @override
      Widget build(BuildContext context) => FPopover(
        controller: controller,
        popoverAnchor: Alignment.bottomLeft,
        childAnchor: Alignment.bottomRight,
        popoverBuilder: (context, controller) => Padding(
          padding: const EdgeInsets.only(left: 20, top: 14, right: 20, bottom: 10),
          child: SizedBox(
            width: 288,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('Dimensions', style: context.theme.typography.base),
                const SizedBox(height: 7),
                Text(
                  'Set the dimensions for the layer.',
                  style: context.theme.typography.sm.copyWith(
                    color: context.theme.colors.mutedForeground,
                    fontWeight: FontWeight.w300,
                  ),
                ),
                const SizedBox(height: 15),
                for (final (index, (label, value)) in [
                  ('Width', '100%'),
                  ('Max. Width', '300px'),
                  ('Height', '25px'),
                  ('Max. Height', 'none'),
                ].indexed) ...[
                  Row(
                    children: [
                      Expanded(child: Text(label, style: context.theme.typography.sm)),
                      Expanded(
                        flex: 2,
                        child: FTextField(autofocus: index == 0, initialText: value),
                      ),
                    ],
                  ),
                  const SizedBox(height: 7),
                ],
              ],
            ),
          ),
        ),
        builder: (context, controller, child) => IntrinsicWidth(
          child: FButton(style: FButtonStyle.outline(), onPress: controller.toggle, child: const Text('Open popover')),
        ),
      );
    }
    ```

  </Tabs.Tab>
</Tabs>

## CLI

To generate and customize this style:

```shell copy
dart run forui style create popover
```

## Usage

### `FPopover(...)`

```dart copy
FPopover(
  controller: FPopoverController(vsync: this),
  style: FPopoverStyle(...),
  popoverAnchor: Alignment.topCenter,
  childAnchor: Alignment.bottomCenter,
  constraints: const FPortalConstraints(),
  spacing: const FPortalSpacing(4),
  overflow: FPortalOverflow.flip,
  offset: Offset.zero,
  groupId: 'popover-group',
  hideRegion: FPopoverHideRegion.excludeChild,
  onTapHide: () {},
  popoverBuilder: (context, controller) => const Placeholder(),
  builder: (context, controller, child) => const Placeholder(),
  child: const Placeholder(),
);
```

## Examples

### Horizontal Alignment

You can change how the popover is aligned to the button.

<Tabs items={['Preview', 'Code']}>
  <Tabs.Tab>
    <Widget name='popover' query={{axis: 'horizontal'}} height={500}/>
  </Tabs.Tab>
  <Tabs.Tab>
    ```dart {5-6} copy
    class _Popover extends StatelessWidget {
      @override
      Widget build(BuildContext context) => FPopover(
        controller: controller,
        popoverAnchor: Alignment.bottomLeft,
        childAnchor: Alignment.bottomRight,
        popoverBuilder: (context, controller) => Padding(
          padding: const EdgeInsets.only(left: 20, top: 14, right: 20, bottom: 10),
          child: SizedBox(
            width: 288,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('Dimensions', style: context.theme.typography.base),
                const SizedBox(height: 7),
                Text(
                  'Set the dimensions for the layer.',
                  style: context.theme.typography.sm.copyWith(
                    color: context.theme.colors.mutedForeground,
                    fontWeight: FontWeight.w300,
                  ),
                ),
                const SizedBox(height: 15),
                for (final (index, (label, value)) in [
                  ('Width', '100%'),
                  ('Max. Width', '300px'),
                  ('Height', '25px'),
                  ('Max. Height', 'none'),
                ].indexed) ...[
                  Row(
                    children: [
                      Expanded(child: Text(label, style: context.theme.typography.sm)),
                      Expanded(
                        flex: 2,
                        child: FTextField(autofocus: index == 0, initialText: value),
                      ),
                    ],
                  ),
                  const SizedBox(height: 7),
                ],
              ],
            ),
          ),
        ),
        builder: (context, controller, child) => IntrinsicWidth(
          child: FButton(style: FButtonStyle.outline(), onPress: controller.toggle, child: const Text('Open popover')),
        ),
      );
    }
    ```

  </Tabs.Tab>
</Tabs>

### Tapping Outside Does Not Close Popover

<Tabs items={['Preview', 'Code']}>
  <Tabs.Tab>
    <Widget name='popover' query={{alignment: 'topCenter', hideRegion: 'none'}} height={500}/>
  </Tabs.Tab>
  <Tabs.Tab>
    ```dart {5} copy
    class _Popover extends StatelessWidget {
      @override
      Widget build(BuildContext context) => FPopover(
        controller: controller,
        hideRegion: FPopoverHideRegion.none,
        popoverBuilder: (context, style) => Padding(
          padding: const EdgeInsets.only(left: 20, top: 14, right: 20, bottom: 10),
          child: SizedBox(
            width: 288,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('Dimensions', style: context.theme.typography.base),
                const SizedBox(height: 7),
                Text(
                  'Set the dimensions for the layer.',
                  style: context.theme.typography.sm.copyWith(
                    color: context.theme.colors.mutedForeground,
                    fontWeight: FontWeight.w300,
                  ),
                ),
                const SizedBox(height: 15),
                for (final (index, (label, value)) in [
                  ('Width', '100%'),
                  ('Max. Width', '300px'),
                  ('Height', '25px'),
                  ('Max. Height', 'none'),
                ].indexed) ...[
                  Row(
                    children: [
                      Expanded(child: Text(label, style: context.theme.typography.sm)),
                      Expanded(
                        flex: 2,
                        child: FTextField(autofocus: index == 0, initialText: value),
                      ),
                    ],
                  ),
                  const SizedBox(height: 7),
                ],
              ],
            ),
          ),
        ),
        builder: (context, controller, child) => IntrinsicWidth(
          child: FButton(style: FButtonStyle.outline(), onPress: controller.toggle, child: const Text('Open popover')),
        ),
      );
    }
    ```

  </Tabs.Tab>
</Tabs>

### Blurred Barrier

<Tabs items={['Preview', 'Code']}>
  <Tabs.Tab>
    <Widget name='popover' variant='blurred' height={500}/>
  </Tabs.Tab>
  <Tabs.Tab>
    ```dart {19-26} copy
    class BlurredPopoverPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) => Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.end,
        children: [
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text('Layer Properties', style: context.theme.typography.xl.copyWith(fontWeight: FontWeight.bold)),
              const SizedBox(height: 20),
              const FTextField(initialText: 'Header Component'),
              const SizedBox(height: 16),
              const FTextField(initialText: 'Navigation Bar'),
              const SizedBox(height: 30),
            ],
          ),
          FPopover(
            style: context.theme.popoverStyle.copyWith(
            barrierFilter: (animation) => ImageFilter.compose(
              outer: ImageFilter.blur(sigmaX: animation * 5, sigmaY: animation * 5),
              inner: ColorFilter.mode(
                Color.lerp(Colors.transparent, Colors.black.withValues(alpha: 0.2), animation)!,
                BlendMode.srcOver,
              ),
            ),
            popoverAnchor: Alignment.topCenter,
            childAnchor: Alignment.bottomCenter,
            popoverBuilder: (context, controller) => Padding(
              padding: const EdgeInsets.only(left: 20, top: 14, right: 20, bottom: 10),
              child: SizedBox(
                width: 288,
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text('Dimensions', style: context.theme.typography.base),
                    const SizedBox(height: 7),
                    Text(
                      'Set the dimensions for the layer.',
                      style: context.theme.typography.sm.copyWith(
                        color: context.theme.colors.mutedForeground,
                        fontWeight: FontWeight.w300,
                      ),
                    ),
                    const SizedBox(height: 15),
                    for (final (index, (label, value)) in [
                      ('Width', '100%'),
                      ('Max. Width', '300px'),
                      ('Height', '25px'),
                      ('Max. Height', 'none'),
                    ].indexed) ...[
                      Row(
                        children: [
                          Expanded(child: Text(label, style: context.theme.typography.sm)),
                          Expanded(
                            flex: 2,
                            child: FTextField(autofocus: index == 0, initialText: value),
                          ),
                        ],
                      ),
                      const SizedBox(height: 7),
                    ],
                  ],
                ),
              ),
            ),
            builder: (context, controller, child) => IntrinsicWidth(
              child: FButton(style: FButtonStyle.outline(), onPress: controller.toggle, child: const Text('Open popover')),
            ),
          ),
        ],
      );
    }
    ```

  </Tabs.Tab>
</Tabs>

### Flip along Axis

The popover can be flipped along the overflowing axis to stay within the viewport boundaries.

<Tabs items={['Preview', 'Code']}>
  <Tabs.Tab>
    <Widget name='popover' query={{alignment: 'bottomCenter', overflow: 'flip'}} height={500}/>
  </Tabs.Tab>
  <Tabs.Tab>
    ```dart {5} copy
    class _Popover extends StatelessWidget {
      @override
      Widget build(BuildContext context) => FPopover(
        controller: controller,
        overflow: FPortalOverflow.flip,
        popoverBuilder: (context, _) => Padding(
          padding: const EdgeInsets.only(left: 20, top: 14, right: 20, bottom: 10),
          child: SizedBox(
            width: 288,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('Dimensions', style: context.theme.typography.base),
                const SizedBox(height: 7),
                Text(
                  'Set the dimensions for the layer.',
                  style: context.theme.typography.sm.copyWith(
                    color: context.theme.colors.mutedForeground,
                    fontWeight: FontWeight.w300,
                  ),
                ),
                const SizedBox(height: 15),
                for (final (index, (label, value)) in [
                  ('Width', '100%'),
                  ('Max. Width', '300px'),
                  ('Height', '25px'),
                  ('Max. Height', 'none'),
                ].indexed) ...[
                  Row(
                    children: [
                      Expanded(child: Text(label, style: context.theme.typography.sm)),
                      Expanded(
                        flex: 2,
                        child: FTextField(autofocus: index == 0, initialText: value),
                      ),
                    ],
                  ),
                  const SizedBox(height: 7),
                ],
              ],
            ),
          ),
        ),
        builder: (context, controller, child) => FButton(
          style: FButtonStyle.outline(),
          mainAxisSize: MainAxisSize.min,
          onPress: controller.toggle,
          child: const Text('Open popover'),
        ),
      );
    }
    ```

  </Tabs.Tab>
</Tabs>

### Slide along Axis

The popover can be slid along the overflowing axis to stay within the viewport boundaries.

<Tabs items={['Preview', 'Code']}>
  <Tabs.Tab>
    <Widget name='popover' query={{overflow: 'slide'}} height={500}/>
  </Tabs.Tab>
  <Tabs.Tab>
    ```dart {5} copy
    class _Popover extends StatelessWidget {
      @override
      Widget build(BuildContext context) => FPopover(
        controller: controller,
        overflow: FPortalOverflow.slide,
        popoverBuilder: (context, controller) => Padding(
          padding: const EdgeInsets.only(left: 20, top: 14, right: 20, bottom: 10),
          child: SizedBox(
            width: 288,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('Dimensions', style: context.theme.typography.base),
                const SizedBox(height: 7),
                Text(
                  'Set the dimensions for the layer.',
                  style: context.theme.typography.sm.copyWith(
                    color: context.theme.colors.mutedForeground,
                    fontWeight: FontWeight.w300,
                  ),
                ),
                const SizedBox(height: 15),
                for (final (index, (label, value)) in [
                  ('Width', '100%'),
                  ('Max. Width', '300px'),
                  ('Height', '25px'),
                  ('Max. Height', 'none'),
                ].indexed) ...[
                  Row(
                    children: [
                      Expanded(child: Text(label, style: context.theme.typography.sm)),
                      Expanded(
                        flex: 2,
                        child: FTextField(autofocus: index == 0, initialText: value),
                      ),
                    ],
                  ),
                  const SizedBox(height: 7),
                ],
              ],
            ),
          ),
        ),
        builder: (context, controller, child) => FButton(
          style: FButtonStyle.outline(),
          mainAxisSize: MainAxisSize.min,
          onPress: controller.toggle,
          child: const Text('Open popover'),
        ),
      );
    }
    ```

  </Tabs.Tab>
</Tabs>

### Allow Overflow

The popover is not shifted to stay within the viewport boundaries, even if it overflows.

<Tabs items={['Preview', 'Code']}>
  <Tabs.Tab>
    <Widget name='popover' query={{overflow: 'allow'}} height={500}/>
  </Tabs.Tab>
  <Tabs.Tab>
    ```dart {5} copy
    class _Popover extends StatelessWidget {
      @override
      Widget build(BuildContext context) => FPopover(
        controller: controller,
        overflow: FPortalOverflow.none,
        popoverBuilder: (context, controller) => Padding(
          padding: const EdgeInsets.only(left: 20, top: 14, right: 20, bottom: 10),
          child: SizedBox(
            width: 288,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('Dimensions', style: context.theme.typography.base),
                const SizedBox(height: 7),
                Text(
                  'Set the dimensions for the layer.',
                  style: context.theme.typography.sm.copyWith(
                    color: context.theme.colors.mutedForeground,
                    fontWeight: FontWeight.w300,
                  ),
                ),
                const SizedBox(height: 15),
                for (final (index, (label, value)) in [
                  ('Width', '100%'),
                  ('Max. Width', '300px'),
                  ('Height', '25px'),
                  ('Max. Height', 'none'),
                ].indexed) ...[
                  Row(
                    children: [
                      Expanded(child: Text(label, style: context.theme.typography.sm)),
                      Expanded(
                        flex: 2,
                        child: FTextField(autofocus: index == 0, initialText: value),
                      ),
                    ],
                  ),
                  const SizedBox(height: 7),
                ],
              ],
            ),
          ),
        ),
        builder: (context, controller, child) => FButton(
          style: FButtonStyle.outline(),
          mainAxisSize: MainAxisSize.min,
          onPress: controller.toggle,
          child: const Text('Open popover'),
        ),
      );
    }
    ```

  </Tabs.Tab>
</Tabs>
